package com.ctsi.oauth.config;

import com.ctsi.auth.integration.ImageResourceOwnerPasswordTokenGranter;
import com.ctsi.auth.vcc.manager.VerificationCodeService;
import com.ctsi.commons.util.StringUtil;
import com.ctsi.commons.util.UtilValidate;
import com.ctsi.commons.util.json.JsonUtil;
import com.ctsi.framework.common.exception.CoreErrorConstant;
import com.ctsi.framework.common.exception.ExceptionFactory;
import com.ctsi.framework.common.response.UnifiedResponse;
import com.ctsi.framework.organise.security.SecurityUser;
import com.ctsi.framework.organise.security.UserInfo;
import com.ctsi.oauth.config.exception.CustomWebReponseExceptionTranslator;
import com.ctsi.oauth.manager.RestClientDetailsServiceImpl;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.*;
import org.springframework.security.oauth2.provider.client.ClientCredentialsTokenGranter;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeTokenGranter;
import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator;
import org.springframework.security.oauth2.provider.implicit.ImplicitTokenGranter;
import org.springframework.security.oauth2.provider.refresh.RefreshTokenGranter;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;

import javax.annotation.Resource;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 授权服务器配置
 * /oauth/authorize：验证
 * /oauth/token：获取token
 * /oauth/confirm_access：用户授权
 * /oauth/error：认证失败
 * /oauth/check_token：资源服务器用来校验token
 * /oauth/token_key：如果jwt模式则可以用此来从认证服务器获取公钥
 * @author zhangjw
 */
@Configuration
@EnableAuthorizationServer
@Import(TokenStoreConfig.class)
public class OAuth2Configuration extends AuthorizationServerConfigurerAdapter {

    private static final Logger logger = LoggerFactory.getLogger(OAuth2Configuration.class);

    @Autowired
    private ExceptionFactory exceptionFactory;

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private MessageSource messageSource;

    @Autowired
    private TokenStore tokenStore;

    @Autowired
    private AuthenticationManager authenticationManager;

    @Resource
    private UserDetailsService userDetailsService;

    @Resource
    private RestClientDetailsServiceImpl restClientDetailsService;

    @Autowired
    private VerificationCodeService verificationCodeService;

    //如下两项非必需，视cloud.security.oauth2.tokenStore而定
    @Autowired(required = false)
    private JwtAccessTokenConverter jwtAccessTokenConverter;

    @Autowired(required = false)
    private TokenEnhancer jwtTokenEnhancer;
    /**
     * client客户端的信息配置
     *
     * 授权码模式（authorization code）
     * 简化模式（implicit）(client为浏览器/前端应用)
     * 密码模式（resource owner password credentials）(用户密码暴露给client端不安全)
     * 客户端模式（client credentials）(主要用于api认证，跟用户无关)
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.withClientDetails(restClientDetailsService);
    }

    @Bean
    public TokenEnhancer tokenEnhancer(){
        return new TokenEnhancer() {
            @Override
            public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
                if (accessToken instanceof DefaultOAuth2AccessToken){
                    DefaultOAuth2AccessToken token= (DefaultOAuth2AccessToken) accessToken;
                    Map<String, Object> additionalInformation = new LinkedHashMap<String, Object>();
                    SecurityUser user = (SecurityUser) authentication.getPrincipal();
                    System.out.println(user.toString());
                    user.getAuthorities().stream().forEach(s-> System.out.println(">>>>>>>>>>>>>>>>>>"+s.getAuthority()));
                    UserInfo info = user.getUserInfo();
                    additionalInformation.put("fullname", UtilValidate.isEmpty(info.getFullName())? "" : info.getFullName());
                    additionalInformation.put("departId",UtilValidate.isEmpty(info.getDepartId())? "" : info.getDepartId());
                    additionalInformation.put("organName",UtilValidate.isEmpty(info.getOrganName())? "" : info.getOrganName());
                    additionalInformation.put("organId",UtilValidate.isEmpty(info.getOrganId())? "" : info.getOrganId());
                    additionalInformation.put("departName",UtilValidate.isEmpty(info.getDepartName())? "" : info.getDepartName());
                    additionalInformation.put("email",UtilValidate.isEmpty(info.getEmail())? "" : info.getEmail());
                    additionalInformation.put("phone",UtilValidate.isEmpty(info.getMobilePhone())? "" : info.getMobilePhone());
                    additionalInformation.put("success",true);
                    additionalInformation.put("userId",info.getUserId());
                    additionalInformation.put("permissions", UtilValidate.isEmpty(info.getPermissions()) ? Collections.emptyList() : info.getPermissions());
                    if (UtilValidate.isEmpty(info.getRoles())) {
                        additionalInformation.put("roles",Collections.emptyList());
                    } else {
                        additionalInformation.put("roles",info.getRoles().stream().map(s -> StringUtil.replaceString(s,"ROLE_","")).collect(Collectors.toList()));
                    }
//                    additionalInformation.put("roles",UtilValidate.isEmpty(info.getRoles()) ? Collections.emptyList() : info.getRoles());
                    additionalInformation.put("tenementId",info.getTenementId());
//                    additionalInformation.put("needChangePwd",false);
                    // 处理权限ID
                    token.setAdditionalInformation(additionalInformation);
                }
                return accessToken;
            }
        };
    }


    /**
     * 声明授权和token的端点以及token的服务的一些配置信息，比如采用什么存储方式、token的有效期等
     * @param endpoints
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        logger.info("oauth server use {} as tokenStore",tokenStore.getClass());
        endpoints.tokenStore(tokenStore)
                .authenticationManager(authenticationManager)
                .userDetailsService(userDetailsService)
                .tokenEnhancer(tokenEnhancer());
        endpoints.exceptionTranslator(exceptionTranslator());

        if (jwtAccessTokenConverter != null && jwtTokenEnhancer != null) {
            TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
            List<TokenEnhancer> enhancers = new ArrayList<>();
            enhancers.add(jwtTokenEnhancer);
            enhancers.add(jwtAccessTokenConverter);
            enhancerChain.setTokenEnhancers(enhancers);
            endpoints.tokenEnhancer(enhancerChain).accessTokenConverter(jwtAccessTokenConverter);
        }
        /**
         * 添加自定义TokenGrantor
         */
        // 先获取初始化部分对象
        endpoints.tokenGranter(new TokenGranter() {
            private CompositeTokenGranter delegate;
            @Override
            public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {
                if (delegate == null) {
                    delegate = new CompositeTokenGranter(getDefaultTokenGranters(endpoints));
                }
                return delegate.grant(grantType, tokenRequest);
            }
        });
    }

    private List<TokenGranter> getDefaultTokenGranters(AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints.getTokenGranter();
        ClientDetailsService clientDetails = restClientDetailsService;
        AuthorizationServerTokenServices tokenServices = endpoints.getTokenServices();
        AuthorizationCodeServices authorizationCodeServices = endpoints.getAuthorizationCodeServices();
        OAuth2RequestFactory requestFactory = endpoints.getOAuth2RequestFactory();

        List<TokenGranter> tokenGranters = new ArrayList<>();
        tokenGranters.add(new AuthorizationCodeTokenGranter(tokenServices, authorizationCodeServices, clientDetails, requestFactory));
        tokenGranters.add(new RefreshTokenGranter(tokenServices, clientDetails, requestFactory));
        ImplicitTokenGranter implicit = new ImplicitTokenGranter(tokenServices, clientDetails, requestFactory);
        tokenGranters.add(implicit);
        tokenGranters.add(new ClientCredentialsTokenGranter(tokenServices, clientDetails, requestFactory));
        if (authenticationManager != null) {
            tokenGranters.add(new ImageResourceOwnerPasswordTokenGranter(authenticationManager, tokenServices, clientDetails, requestFactory,verificationCodeService));
        }
        return tokenGranters;
    }

    /**
     * 声明安全约束，哪些允许访问，哪些不允许访问
     * @param security
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) {
        security
                .allowFormAuthenticationForClients()
                .tokenKeyAccess("permitAll()")
                .checkTokenAccess("isAuthenticated()");
        security.authenticationEntryPoint(custormAuthExceptionEntryPoint());
        security.accessDeniedHandler(custormAccessDeniedHandler());
//        // 登陆历史过滤器
//        security.addTokenEndpointAuthenticationFilter(userLoginRecordFilter);
    }

    /**
     * 退出时的处理策略配置
     *
     * @return logout success handler
     */
    @Bean
    public LogoutSuccessHandler logoutSuccessHandler() {
        return new LogoutSuccessHandler() {
            @Override
            public void onLogoutSuccess(HttpServletRequest request,
                                        HttpServletResponse response,
                                        Authentication authentication) throws IOException, ServletException {

                //TODO: 处理退出逻辑

            }
        };
    }
    /**
     * 异常转换
     * @return
     */
    @Bean
    public WebResponseExceptionTranslator exceptionTranslator(){
        return new CustomWebReponseExceptionTranslator();
    }

    @Bean
    public AccessDeniedHandler custormAccessDeniedHandler(){
        return new AccessDeniedHandler() {
            @Override
            public void handle(HttpServletRequest request, HttpServletResponse response,
                               AccessDeniedException accessDeniedException) throws IOException, ServletException {
                // TODO: 处理被拒绝
//                response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
//                response.setStatus(HttpStatus.UNAUTHORIZED.value());
//                UnifiedResponse<Void> ur = null;
//                if(accessDeniedException instanceof AuthorizationServiceException) {
//                    logger.error("Authorization Service occur error: {} ",accessDeniedException.getMessage());
//                    ur = UnifiedResponse.error(CoreErrorConstant.ERROR_CODE,exceptionFactory.getResource(CoreErrorConstant.ERROR_CODE));
//                }else if (accessDeniedException instanceof CsrfException || accessDeniedException instanceof org.springframework.security.web.server.csrf.CsrfException){
//                    // TODO：暂时不处理csrf异常
//                    logger.error("CSRF occur error: {} ",accessDeniedException.getMessage());
//                    ur = UnifiedResponse.error(CoreErrorConstant.ERROR_CODE,exceptionFactory.getResource(CoreErrorConstant.ERROR_CODE));
//                }else if (accessDeniedException instanceof  AccessDeniedException) {
//                    logger.warn("request access denied may be insufficient privileges,error is {}",accessDeniedException.getMessage());
//                    ur = UnifiedResponse.error(CoreErrorConstant.PERMISSION_DENIED_ERROR,exceptionFactory.getResource(CoreErrorConstant.PERMISSION_DENIED_ERROR));
//                }
//                response.getWriter().write(objectMapper.writeValueAsString(ur));

                logger.warn("服务被拒绝");
            }
        };
    }

    @Bean
    public AuthenticationEntryPoint custormAuthExceptionEntryPoint(){
        return new AuthenticationEntryPoint() {
            @Override
            public void commence(HttpServletRequest request, HttpServletResponse response,AuthenticationException authException) throws IOException, ServletException {
               response.setCharacterEncoding("UTF-8");
               response.setContentType("application/json; charset=utf-8");
               PrintWriter out = response.getWriter();
               UnifiedResponse unifiedResponse = UnifiedResponse.error(CoreErrorConstant.TOKEN_AUTH_CHECK_ERROR,"请重新登陆!");
               out.print(JsonUtil.toJson(unifiedResponse));
            }
        };
    }


}
